<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<!-- 
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License. 
-->
<html>
<head>
    <link type="text/css" rel="stylesheet" href="https://struts.apache.org/css/default.css">
    <style type="text/css">
        .dp-highlighter {
            width:95% !important;
        }
    </style>
    <style type="text/css">
        .footer {
            background-image:      url('https://cwiki.apache.org/confluence/images/border/border_bottom.gif');
            background-repeat:     repeat-x;
            background-position:   left top;
            padding-top:           4px;
            color:                 #666;
        }
    </style>
    <link href='https://struts.apache.org/highlighter/style/shCoreStruts.css' rel='stylesheet' type='text/css' />
    <link href='https://struts.apache.org/highlighter/style/shThemeStruts.css' rel='stylesheet' type='text/css' />
    <script src='https://struts.apache.org/highlighter/js/shCore.js' type='text/javascript'></script>
    <script src='https://struts.apache.org/highlighter/js/shBrushPlain.js' type='text/javascript'></script>
    <script src='https://struts.apache.org/highlighter/js/shBrushXml.js' type='text/javascript'></script>
    <script src='https://struts.apache.org/highlighter/js/shBrushJava.js' type='text/javascript'></script>
    <script src='https://struts.apache.org/highlighter/js/shBrushJScript.js' type='text/javascript'></script>
    <script src='https://struts.apache.org/highlighter/js/shBrushGroovy.js' type='text/javascript'></script>
    <script src='https://struts.apache.org/highlighter/js/shBrushBash.js' type='text/javascript'></script>
    <script type="text/javascript">
        SyntaxHighlighter.defaults['toolbar'] = false;
        SyntaxHighlighter.all();
    </script>
    <script type="text/javascript" language="javascript">
        var hide = null;
        var show = null;
        var children = null;

        function init() {
            /* Search form initialization */
            var form = document.forms['search'];
            if (form != null) {
                form.elements['domains'].value = location.hostname;
                form.elements['sitesearch'].value = location.hostname;
            }

            /* Children initialization */
            hide = document.getElementById('hide');
            show = document.getElementById('show');
            children = document.all != null ?
                    document.all['children'] :
                    document.getElementById('children');
            if (children != null) {
                children.style.display = 'none';
                show.style.display = 'inline';
                hide.style.display = 'none';
            }
        }

        function showChildren() {
            children.style.display = 'block';
            show.style.display = 'none';
            hide.style.display = 'inline';
        }

        function hideChildren() {
            children.style.display = 'none';
            show.style.display = 'inline';
            hide.style.display = 'none';
        }
    </script>
    <title>RestfulActionMapper</title>
</head>
<body onload="init()">
<table border="0" cellpadding="2" cellspacing="0" width="100%">
    <tr class="topBar">
        <td align="left" valign="middle" class="topBarDiv" align="left" nowrap>
            &nbsp;<a href="home.html">Home</a>&nbsp;&gt;&nbsp;<a href="guides.html">Guides</a>&nbsp;&gt;&nbsp;<a href="core-developers-guide.html">Core Developers Guide</a>&nbsp;&gt;&nbsp;<a href="actionmapper.html">ActionMapper</a>&nbsp;&gt;&nbsp;<a href="restfulactionmapper.html">RestfulActionMapper</a>
        </td>
        <td align="right" valign="middle" nowrap>
            <form name="search" action="https://www.google.com/search" method="get">
                <input type="hidden" name="ie" value="UTF-8" />
                <input type="hidden" name="oe" value="UTF-8" />
                <input type="hidden" name="domains" value="" />
                <input type="hidden" name="sitesearch" value="" />
                <input type="text" name="q" maxlength="255" value="" />
                <input type="submit" name="btnG" value="Google Search" />
            </form>
        </td>
    </tr>
</table>

<div id="PageContent">
    <div class="pageheader" style="padding: 6px 0px 0px 0px;">
        <!-- We'll enable this once we figure out how to access (and save) the logo resource -->
        <!--img src="/wiki/images/confluence_logo.gif" style="float: left; margin: 4px 4px 4px 10px;" border="0"-->
        <div style="margin: 0px 10px 0px 10px" class="smalltext">Apache Struts 2 Documentation</div>
        <div style="margin: 0px 10px 8px 10px"  class="pagetitle">RestfulActionMapper</div>

        <div class="greynavbar" align="right" style="padding: 2px 10px; margin: 0px;">
            <a href="https://cwiki.apache.org/confluence/pages/editpage.action?pageId=33965">
                <img src="https://cwiki.apache.org/confluence/images/icons/notep_16.gif"
                     height="16" width="16" border="0" align="absmiddle" title="Edit Page"></a>
            <a href="https://cwiki.apache.org/confluence/pages/editpage.action?pageId=33965">Edit Page</a>
            &nbsp;
            <a href="https://cwiki.apache.org/confluence/pages/listpages.action?key=WW">
                <img src="https://cwiki.apache.org/confluence/images/icons/browse_space.gif"
                     height="16" width="16" border="0" align="absmiddle" title="Browse Space"></a>
            <a href="https://cwiki.apache.org/confluence/pages/listpages.action?key=WW">Browse Space</a>
            &nbsp;
            <a href="https://cwiki.apache.org/confluence/pages/createpage.action?spaceKey=WW&fromPageId=33965">
                <img src="https://cwiki.apache.org/confluence/images/icons/add_page_16.gif"
                     height="16" width="16" border="0" align="absmiddle" title="Add Page"></a>
            <a href="https://cwiki.apache.org/confluence/pages/createpage.action?spaceKey=WW&fromPageId=33965">Add Page</a>
            &nbsp;
            <a href="https://cwiki.apache.org/confluence/pages/createblogpost.action?spaceKey=WW&fromPageId=33965">
                <img src="https://cwiki.apache.org/confluence/images/icons/add_blogentry_16.gif"
                     height="16" width="16" border="0" align="absmiddle" title="Add News"></a>
            <a href="https://cwiki.apache.org/confluence/pages/createblogpost.action?spaceKey=WW&fromPageId=33965">Add News</a>
        </div>
    </div>

    <div class="pagecontent">
        <div class="wiki-content">
            <div id="ConfluenceContent"><h2 id="RestfulActionMapper-RestfulActionMapper">RestfulActionMapper</h2>

<p>A custom action mapper using the following format:</p>
<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>http://HOST/ACTION_NAME/PARAM_NAME1/PARAM_VALUE1/PARAM_NAME2/PARAM_VALUE2</pre>
</div></div>

<p>You can have as many parameters you'd like to use. Alternatively the URL can be shortened to the following:</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>http://HOST/ACTION_NAME/PARAM_VALUE1/PARAM_NAME2/PARAM_VALUE2</pre>
</div></div>

<p>This is the same as:</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>http://HOST/ACTION_NAME/ACTION_NAME + "Id"/PARAM_VALUE1/PARAM_NAME2/PARAM_VALUE2</pre>
</div></div>

<p>Suppose for example we would like to display some articles by id at using the following URL sheme:</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>http://HOST/article/Id</pre>
</div></div>

<p>Your action just needs a setArticleId() method, and requests such as /article/1, /article/2, etc will all map to that URL pattern.</p>

<h2 id="RestfulActionMapper-Restful2ActionMapper">Restful2ActionMapper</h2>

<p>Improved restful action mapper that adds several ReST-style improvements to action mapping, but supports fully-customized URL's via XML. The two primary REST enhancements are:</p>
<ul><li>If the method is not specified (via '!' or 'method:' prefix), the method is "guessed" at using ReST-style conventions that examine the URL and the HTTP method.</li><li>Parameters are extracted from the action name, if parameter name/value pairs are specified using PARAM_NAME/PARAM_VALUE syntax.</li></ul>


<p>These two improvements allow a GET request for 'category/action/movie/Thrillers' to be mapped to the action name 'movie' with an id of 'Thrillers' with an extra parameter named 'category' with a value of 'action'.  A single action mapping can then handle all CRUD operations using wildcards, e.g.</p>

<div class="code panel pdl" style="border-width: 1px;"><div class="codeContent panelContent pdl">
<pre class="brush: xml; gutter: false; theme: Default" style="font-size:12px;">
&lt;action name="movie/*" className="app.MovieAction"&gt;
    &lt;param name="id"&gt;{1}&lt;/param&gt;
    ...
&lt;/action&gt;
</pre>
</div></div>

<p>This mapper supports the following parameters:</p>
<ul><li><em>struts.mapper.idParameterName</em> - if set, this value will be the name of the parameter under which the id is stored.  The id will then be removed from the action name.  This allows restful actions to not require wildcards.</li></ul>


<p>The following URL's will invoke its methods:</p>
<div class="table-wrap"><table class="confluenceTable"><tbody><tr><th colspan="1" rowspan="1" class="confluenceTh"><p>Request                    </p></th><th colspan="1" rowspan="1" class="confluenceTh"><p>method called</p></th></tr><tr><td colspan="1" rowspan="1" class="confluenceTd"><p> GET: /movie/               </p></td><td colspan="1" rowspan="1" class="confluenceTd"><p>method="index" </p></td></tr><tr><td colspan="1" rowspan="1" class="confluenceTd"><p> GET: /movie/Thrillers      </p></td><td colspan="1" rowspan="1" class="confluenceTd"><p> method="view", id="Thrillers" </p></td></tr><tr><td colspan="1" rowspan="1" class="confluenceTd"><p> GET: /movie/Thrillers!edit </p></td><td colspan="1" rowspan="1" class="confluenceTd"><p> method="edit", id="Thrillers" </p></td></tr><tr><td colspan="1" rowspan="1" class="confluenceTd"><p> GET: /movie/new            </p></td><td colspan="1" rowspan="1" class="confluenceTd"><p> method="editNew" </p></td></tr><tr><td colspan="1" rowspan="1" class="confluenceTd"><p> POST: /movie/              </p></td><td colspan="1" rowspan="1" class="confluenceTd"><p> method="create" </p></td></tr><tr><td colspan="1" rowspan="1" class="confluenceTd"><p> PUT: /movie/Thrillers      </p></td><td colspan="1" rowspan="1" class="confluenceTd"><p> method="update", id="Thrillers" </p></td></tr><tr><td colspan="1" rowspan="1" class="confluenceTd"><p> DELETE: /movie/Thrillers   </p></td><td colspan="1" rowspan="1" class="confluenceTd"><p> method="remove", id="Thrillers" </p></td></tr></tbody></table></div>


<p>To simulate the HTTP methods PUT and DELETE, since they aren't supported by HTML, the HTTP parameter "__http_method" will be used.</p>

<p>The syntax and design for this feature was inspired by the REST support in Ruby on Rails. See <a shape="rect" class="external-link" href="http://ryandaigle.com/articles/2006/08/01/whats-new-in-edge-rails-simply-restful-support-and-how-to-use-it" rel="nofollow">Simple RESTful support</a></p>

<h3 id="RestfulActionMapper-Example">Example</h3>
<p>To use the Restful2ActionMapper in an existing struts application we have to change the strus.mapper.class constant and let it point to the Restful2ActionMapper</p>

<div class="code panel pdl" style="border-width: 1px;"><div class="codeContent panelContent pdl">
<pre class="brush: xml; gutter: false; theme: Default" style="font-size:12px;">
&lt;constant name="struts.mapper.class" value="org.apache.struts2.dispatcher.mapper.Restful2ActionMapper" /&gt;
</pre>
</div></div>

<p>The problem with the above approach is that we may break existing actions because the Restful2ActionMapper tries to guess the method name using conventions that aren't applicable to normal action classes.</p>

<p>To overcome the above problem, we have to use a different action mapper depending on the url we want to process. REST actions will be processed by the Restful2ActionMapper and non-REST actions by the DefaultActionMapper</p>

<p>To achieve that we have to rely on namespaces and the PrefixBasedActionMapper that can choose which action mapper to use for a particular url based on a prefix (the action namespace).</p>

<p>To put everything together, we create a package for our rest actions</p>

<div class="code panel pdl" style="border-width: 1px;"><div class="codeContent panelContent pdl">
<pre class="brush: xml; gutter: false; theme: Default" style="font-size:12px;">
&lt;package name="rest" namespace="/rest" extends="struts-default"&gt;
    ....interceptor config
    &lt;action name="movie/*" class="app.MovieAction"&gt;
        &lt;param name="id"&gt;{1}&lt;/param&gt;
        ....results
    &lt;/action&gt;
    ....
&lt;/package&gt;
</pre>
</div></div>

<p>All other actions remain in their existing packages and namespaces we use the PrefixBasedActionMapper telling it to use the Restful2ActionMapper for actions in the /rest namespace and the DefaultActionMapper for all other actions</p>

<div class="code panel pdl" style="border-width: 1px;"><div class="codeContent panelContent pdl">
<pre class="brush: xml; gutter: false; theme: Default" style="font-size:12px;">
&lt;constant name="struts.mapper.class" value="org.apache.struts2.dispatcher.mapper.PrefixBasedActionMapper" /&gt;
&lt;constant name="struts.mapper.prefixMapping" value="/rest:restful2,:struts" /&gt;
</pre>
</div></div>

<p>For the Restful2ActionMapper to work we also have to set</p>

<div class="code panel pdl" style="border-width: 1px;"><div class="codeContent panelContent pdl">
<pre class="brush: xml; gutter: false; theme: Default" style="font-size:12px;">
&lt;constant name="struts.enable.SlashesInActionNames" value="true" /&gt;
&lt;constant name="struts.mapper.alwaysSelectFullNamespace" value="false" /&gt;
</pre>
</div></div>

<h3 id="RestfulActionMapper-Unittesting">Unit testing</h3>

<p>Below you will find a simple unit test to test how to test actions when <code>Restful2ActionMapper</code> is used.</p>

<div class="code panel pdl" style="border-width: 1px;"><div class="codeContent panelContent pdl">
<pre class="brush: java; gutter: false; theme: Default" style="font-size:12px;">
public class MovieActionTest extends StrutsJUnit4TestCase&lt;MovieActionTest&gt;{
    
    @Before
    public void setUp() throws Exception {
        //assumes Basic authentication
        super.setUp();
        String credentials = "username:password";
        request.addHeader("authorization", "BASIC " + Base64.encodeBase64String(credentials.getBytes()));
    }
        
    @Test
    public void testIndex() throws Exception {
        request.setMethod("get"); //Http method should be set
        
        ActionProxy proxy = getActionProxy("/rest/movie/");                        
      
        proxy.setExecuteResult(false);
        String result = proxy.execute();
        
       //assertions ...        
    }
    
    @Test
    public void testView() throws Exception {
        request.setMethod("get"); //Http method should be set
              
        ActionProxy proxy = getActionProxy("/rest/movie/1");                        
        MovieAction movieAction = MovieAction.class.cast(proxy.getAction());
           
        proxy.setExecuteResult(false);
        
        String result = proxy.execute();
        //assertions ...
        assertEquals("1", movieAction.getId());         
    }
}
</pre>
</div></div>

<p>Thanks to Antonios Gkogkakis for the examples!</p></div>
        </div>

        
    </div>
</div>
<div class="footer">
    Generated by CXF SiteExporter
</div>
</body>
</html>
